Skip to content

Add support for Email Logs API#100

Open
mklocek wants to merge 1 commit intomainfrom
email-logs-api
Open

Add support for Email Logs API#100
mklocek wants to merge 1 commit intomainfrom
email-logs-api

Conversation

@mklocek
Copy link
Contributor

@mklocek mklocek commented Mar 13, 2026

Motivation

Add support for Email Logs API

Summary by CodeRabbit

  • New Features

    • Added Email Logs API client: list email logs with filters and cursor pagination, iterate across pages, and retrieve single messages; introduced message and event data types.
  • Documentation

    • README updated to include Email Logs API.
  • Examples

    • Added a Ruby example showing authentication, listing with filters/pagination, and fetching a message.
  • Tests

    • Added RSpec tests and recorded fixtures for listing, filtering, pagination, retrieval, and not-found handling.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Warning

Rate limit exceeded

@mklocek has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 30cdf101-b94b-451f-9a68-31bdae22af5e

📥 Commits

Reviewing files that changed from the base of the PR and between 7bea2d7 and 263f97e.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • README.md
  • examples/email_logs_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/email_log_event.rb
  • lib/mailtrap/email_log_event_details.rb
  • lib/mailtrap/email_log_message.rb
  • lib/mailtrap/email_logs_api.rb
  • lib/mailtrap/email_logs_list_response.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/with_filters_and_search_after/sends_correct_query_params_and_maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/mailtrap/email_log_event_details_spec.rb
  • spec/mailtrap/email_log_event_spec.rb
  • spec/mailtrap/email_log_message_spec.rb
  • spec/mailtrap/email_logs_api_spec.rb
📝 Walkthrough

Walkthrough

Adds a new Email Logs API surface: Mailtrap::EmailLogsAPI with list/get methods and cursor pagination, DTOs for messages, events, and event details, example script, lib require, VCR fixtures, RSpec tests, and a CHANGELOG entry.

Changes

Cohort / File(s) Summary
Docs / Changelog
README.md, CHANGELOG.md
Added README entry for Email Logs API and an Unreleased changelog note.
Example
examples/email_logs_api.rb
New example showing listing with filters, cursor pagination, enumerator usage, and single-message retrieval with events.
Library entry
lib/mailtrap.rb
Added require_relative 'mailtrap/email_logs_api' to load the new API module.
API Client
lib/mailtrap/email_logs_api.rb
New Mailtrap::EmailLogsAPI class with list(filters:, search_after:), list_each(filters: nil, &block), get(sending_message_id); builds deepObject-style query params, handles pagination, maps responses to domain DTOs, and raises errors on 404.
DTOs
lib/mailtrap/email_log_message.rb, lib/mailtrap/email_log_event.rb, lib/mailtrap/email_log_event_details.rb, lib/mailtrap/email_logs_list_response.rb
Added Struct-based DTOs: EmailLogMessage, EmailLogEvent, per-event detail structs with builder, and EmailLogsListResponse.
Tests
spec/mailtrap/email_logs_api_spec.rb, spec/mailtrap/email_log_message_spec.rb, spec/mailtrap/email_log_event_spec.rb, spec/mailtrap/email_log_event_details_spec.rb
Added RSpec suites covering mapping, filters/query param encoding, pagination/list_each behavior, event detail construction, and get error handling.
VCR Fixtures
spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/...
Added VCR cassettes for list (with/without filters), get (success mapping with events and raw_message_url), and get 404 error scenario.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant EmailLogsAPI
    participant HTTPClient as HTTP Client
    participant APIServer as API Server
    participant Mapper as Response Mapper

    Client->>EmailLogsAPI: list(filters:, search_after:)
    EmailLogsAPI->>EmailLogsAPI: build_list_query_params(filters, search_after)
    EmailLogsAPI->>HTTPClient: GET /api/accounts/{id}/email_logs?filters...
    HTTPClient->>APIServer: HTTP GET
    APIServer-->>HTTPClient: 200 OK (messages, total_count, next_page_cursor)
    HTTPClient-->>EmailLogsAPI: Response body
    EmailLogsAPI->>Mapper: map response to EmailLogsListResponse
    Mapper->>Mapper: build EmailLogMessage and nested EmailLogEvent(+details)
    Mapper-->>EmailLogsAPI: EmailLogsListResponse
    EmailLogsAPI-->>Client: return EmailLogsListResponse
Loading
sequenceDiagram
    actor Client
    participant EmailLogsAPI
    participant HTTPClient as HTTP Client
    participant APIServer as API Server
    participant Mapper as Response Mapper

    Client->>EmailLogsAPI: get(sending_message_id)
    EmailLogsAPI->>HTTPClient: GET /api/accounts/{id}/email_logs/{message_id}
    HTTPClient->>APIServer: HTTP GET
    alt Message Found
        APIServer-->>HTTPClient: 200 OK (message)
        HTTPClient-->>EmailLogsAPI: Response body
        EmailLogsAPI->>Mapper: map to EmailLogMessage
        Mapper->>Mapper: build EmailLogEvent array and details
        Mapper-->>EmailLogsAPI: EmailLogMessage
        EmailLogsAPI-->>Client: return EmailLogMessage
    else Message Not Found
        APIServer-->>HTTPClient: 404 Not Found
        HTTPClient-->>EmailLogsAPI: Error response
        EmailLogsAPI-->>Client: raise Mailtrap::Error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • DagonWat
  • IgorDobryn

Poem

🐰 I hopped through logs with nimble paws,

Filters like carrots, cursored cause,
Messages found, events in a line,
Raw URLs gleam, timestamps shine,
My whiskers dance — the API's divine!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete, providing only a motivation statement without detailing the actual changes made, testing instructions, or other required template sections. Expand the description to include a detailed 'Changes' section listing the new API client, DTOs, and files added; add a 'How to test' section with testing instructions; and consider including relevant context about the Email Logs API functionality.
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding support for the Email Logs API, which aligns with the substantial additions of new API client, DTOs, examples, and tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch email-logs-api
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mklocek mklocek marked this pull request as ready for review March 13, 2026 14:24
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
spec/mailtrap/email_log_message_spec.rb (1)

27-29: Test intent says “all attributes” but excludes optional fields.

Consider asserting raw_message_url and events explicitly (even as nil) so the claim and coverage match.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/mailtrap/email_log_message_spec.rb` around lines 27 - 29, The spec "it
'creates a message with all attributes'" currently uses expect(message).to
have_attributes(attributes) but the attributes hash omits optional fields;
update the test so the claim matches reality by adding raw_message_url and
events to the asserted attributes (set them to nil if that's the expected value)
or add explicit expectations like expect(message.raw_message_url).to be_nil and
expect(message.events).to be_nil; refer to the test subject `message`, the
example name, and the `attributes` hash when making the change.
examples/email_logs_api.rb (1)

3-11: Consider reordering to favor environment variables as the primary approach.

The example hardcodes credentials first, then shows the environment variable approach as a comment. For better security practices in example code, consider swapping these:

♻️ Suggested reordering
-account_id = 3229
-client = Mailtrap::Client.new(api_key: 'your-api-key')
-email_logs = Mailtrap::EmailLogsAPI.new(account_id, client)
-
 # Set your API credentials as environment variables
 # export MAILTRAP_API_KEY='your-api-key'
 # export MAILTRAP_ACCOUNT_ID=your-account-id
-#
-# email_logs = Mailtrap::EmailLogsAPI.new
+email_logs = Mailtrap::EmailLogsAPI.new
+
+# Or manually pass credentials:
+# account_id = 3229
+# client = Mailtrap::Client.new(api_key: 'your-api-key')
+# email_logs = Mailtrap::EmailLogsAPI.new(account_id, client)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/email_logs_api.rb` around lines 3 - 11, Swap the example to use
environment variables as the primary credential approach: instantiate
Mailtrap::Client and Mailtrap::EmailLogsAPI using ENV['MAILTRAP_API_KEY'] and
ENV['MAILTRAP_ACCOUNT_ID'] (or call Mailtrap::EmailLogsAPI.new with no args if
that constructor reads ENV) at the top, and move the hardcoded account_id and
Mailtrap::Client.new(api_key: 'your-api-key') example into a commented
“alternative” section; update the adjacent comments to show export instructions
and mention the hardcoded snippet as an optional fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/mailtrap/email_log_event_details.rb`:
- Around line 52-56: The build method in EmailLogEventDetails currently assumes
DETAIL_STRUCTS[event_type.to_s] exists and will raise NoMethodError when
event_type is nil or unknown; update EmailLogEventDetails.build to handle a
missing struct_class by checking the result of DETAIL_STRUCTS[event_type.to_s]
(and event_type.nil?) and either (a) return a sensible default/ fallback struct
(or a generic OpenStruct) populated with the sliced attrs, or (b) raise a clear
ArgumentError/RuntimeError with a descriptive message including the unknown
event_type; ensure you reference DETAIL_STRUCTS and struct_class in the change
so callers get a graceful/failing-but-descriptive behavior from
EmailLogEventDetails.build.

---

Nitpick comments:
In `@examples/email_logs_api.rb`:
- Around line 3-11: Swap the example to use environment variables as the primary
credential approach: instantiate Mailtrap::Client and Mailtrap::EmailLogsAPI
using ENV['MAILTRAP_API_KEY'] and ENV['MAILTRAP_ACCOUNT_ID'] (or call
Mailtrap::EmailLogsAPI.new with no args if that constructor reads ENV) at the
top, and move the hardcoded account_id and Mailtrap::Client.new(api_key:
'your-api-key') example into a commented “alternative” section; update the
adjacent comments to show export instructions and mention the hardcoded snippet
as an optional fallback.

In `@spec/mailtrap/email_log_message_spec.rb`:
- Around line 27-29: The spec "it 'creates a message with all attributes'"
currently uses expect(message).to have_attributes(attributes) but the attributes
hash omits optional fields; update the test so the claim matches reality by
adding raw_message_url and events to the asserted attributes (set them to nil if
that's the expected value) or add explicit expectations like
expect(message.raw_message_url).to be_nil and expect(message.events).to be_nil;
refer to the test subject `message`, the example name, and the `attributes` hash
when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c132c193-ccee-4f99-a377-9cc692da37f6

📥 Commits

Reviewing files that changed from the base of the PR and between 457328b and 0c2db29.

📒 Files selected for processing (16)
  • README.md
  • examples/email_logs_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/email_log_event.rb
  • lib/mailtrap/email_log_event_details.rb
  • lib/mailtrap/email_log_message.rb
  • lib/mailtrap/email_logs_api.rb
  • lib/mailtrap/email_logs_list_response.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/with_filters_and_search_after/sends_correct_query_params_and_maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/mailtrap/email_log_event_details_spec.rb
  • spec/mailtrap/email_log_event_spec.rb
  • spec/mailtrap/email_log_message_spec.rb
  • spec/mailtrap/email_logs_api_spec.rb

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
lib/mailtrap/email_logs_api.rb (1)

104-110: ⚠️ Potential issue | 🟠 Major

Prevent one unknown event type from aborting full response mapping.

At Lines 104-110, EmailLogEventDetails.build exceptions bubble up. If the API introduces a new event_type, #list/#get can fail entirely instead of returning the rest of the message data.

Proposed fix
 def build_events(events_array)
   Array(events_array).map do |e|
+    details = begin
+      EmailLogEventDetails.build(e[:event_type], e[:details] || {})
+    rescue ArgumentError
+      nil
+    end
+
     EmailLogEvent.new(
       event_type: e[:event_type],
       created_at: e[:created_at],
-      details: EmailLogEventDetails.build(e[:event_type], e[:details])
+      details: details
     )
   end
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mailtrap/email_logs_api.rb` around lines 104 - 110, build_events
currently maps events_array and calls EmailLogEventDetails.build for each event
which can raise and abort the whole mapping; modify build_events (the method
that constructs EmailLogEvent instances) to rescue exceptions raised by
EmailLogEventDetails.build for each individual event, log or record the error,
and fall back to a safe default (e.g. nil or an UnknownEmailLogEventDetails
placeholder) for the details so a single unknown/new event_type does not break
the entire response; keep the rest of the EmailLogEvent construction unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/mailtrap/email_log_event_details.rb`:
- Around line 53-58: The build method calls hash.slice(...) which will raise if
hash is nil; update EmailLogEventDetails.build to guard against nil details by
coercing the incoming hash to an empty hash before calling slice (e.g., replace
uses of hash.slice(...) with (hash || {}).slice(...) or assign hash = {} if
nil), keeping the existing lookup via DETAIL_STRUCTS and the subsequent
struct_class.new(**attrs) behavior; reference the build method and
DETAIL_STRUCTS to locate the change.

In `@spec/mailtrap/email_logs_api_spec.rb`:
- Around line 6-7: The spec is flaky because let(:account_id) uses ENV.fetch and
can diverge from the hardcoded 1_111_111 used later; change let(:account_id) in
spec/mailtrap/email_logs_api_spec.rb to a deterministic literal (e.g.,
1_111_111) so it matches the cassette/test expectations, and update any other
occurrences (the places referenced on Lines 55-56) to use the same constant
value to keep VCR cassette paths and recordings stable.

---

Duplicate comments:
In `@lib/mailtrap/email_logs_api.rb`:
- Around line 104-110: build_events currently maps events_array and calls
EmailLogEventDetails.build for each event which can raise and abort the whole
mapping; modify build_events (the method that constructs EmailLogEvent
instances) to rescue exceptions raised by EmailLogEventDetails.build for each
individual event, log or record the error, and fall back to a safe default (e.g.
nil or an UnknownEmailLogEventDetails placeholder) for the details so a single
unknown/new event_type does not break the entire response; keep the rest of the
EmailLogEvent construction unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8378af5-4a68-405f-8604-3c4db4a3514f

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2db29 and 75aa89a.

📒 Files selected for processing (16)
  • README.md
  • examples/email_logs_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/email_log_event.rb
  • lib/mailtrap/email_log_event_details.rb
  • lib/mailtrap/email_log_message.rb
  • lib/mailtrap/email_logs_api.rb
  • lib/mailtrap/email_logs_list_response.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/with_filters_and_search_after/sends_correct_query_params_and_maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/mailtrap/email_log_event_details_spec.rb
  • spec/mailtrap/email_log_event_spec.rb
  • spec/mailtrap/email_log_message_spec.rb
  • spec/mailtrap/email_logs_api_spec.rb
🚧 Files skipped from review as they are similar to previous changes (11)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • lib/mailtrap/email_logs_list_response.rb
  • spec/mailtrap/email_log_message_spec.rb
  • lib/mailtrap/email_log_event.rb
  • spec/mailtrap/email_log_event_details_spec.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/with_filters_and_search_after/sends_correct_query_params_and_maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • lib/mailtrap/email_log_message.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
  • README.md
  • examples/email_logs_api.rb

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
spec/mailtrap/email_log_message_spec.rb (1)

4-22: Consider adding optional attributes to test coverage.

The test attributes hash does not include raw_message_url and events fields, which are part of EmailLogMessage according to the API response mapping. While these fields are optional (returned only from the get endpoint), adding them to the test would ensure complete coverage of the DTO's initialization behavior.

💡 Suggested addition
       template_variables: {},
       opens_count: 2,
-      clicks_count: 1
+      clicks_count: 1,
+      raw_message_url: 'https://example.com/raw',
+      events: []
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/mailtrap/email_log_message_spec.rb` around lines 4 - 22, The spec's
attributes hash used to initialize EmailLogMessage is missing optional fields
raw_message_url and events; update the let(:attributes) hash in
spec/mailtrap/email_log_message_spec.rb to include raw_message_url (string URL)
and events (array of event hashes or empty array) so EmailLogMessage
initialization and its handling of optional API response fields are covered by
the test.
spec/mailtrap/email_log_event_details_spec.rb (1)

3-62: Consider adding test coverage for remaining event types.

The tests cover delivery, click, and bounce event types, but based on the implementation context, there are additional event types: open, spam, unsubscribe, reject, and suspension. While the current tests demonstrate the pattern works, adding at least one test for each struct type would provide more comprehensive coverage and document the expected attributes for each.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/mailtrap/email_log_event_details_spec.rb` around lines 3 - 62, Add tests
for the remaining event types by extending the Mailtrap::EmailLogEventDetails
spec: for each event_type 'open', 'spam', 'unsubscribe', 'reject', and
'suspension' call described_class.build(event_type, payload) and assert the
returned object is the appropriate struct (described_class::Open, ::Spam,
::Unsubscribe, ::Reject, ::Suspension) and has the expected attributes for that
type (mirror the pattern used for Delivery/Click/Bounce tests), using be_a(...)
and have_attributes(...) to verify attribute names and example values; keep the
tests consistent with existing examples so they validate .build behavior for all
known DETAIL_STRUCTS.
lib/mailtrap/email_logs_api.rb (1)

13-13: Unused response_class assignment.

The response_class is set to EmailLogMessage but is never used since this class implements custom list and get methods instead of using base_list and base_get from BaseAPI. Consider removing this line to avoid confusion, or add a comment explaining if it's intended for future use or external introspection.

🧹 Proposed removal
   class EmailLogsAPI
     include BaseAPI

-    self.response_class = EmailLogMessage
-
     # Lists email logs with optional filters and cursor-based pagination.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mailtrap/email_logs_api.rb` at line 13, The file sets response_class =
EmailLogMessage but that assignment is unused because the class implements
custom list and get methods instead of leveraging BaseAPI's base_list/base_get;
remove the unused response_class = EmailLogMessage line from the EmailLogsAPI
class (or alternatively add a concise comment above it indicating it's
intentional/for external introspection) so the declaration no longer confuses
readers — search for response_class, EmailLogMessage, list, get, BaseAPI,
base_list and base_get in the EmailLogsAPI implementation to locate and apply
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/mailtrap/email_logs_api.rb`:
- Around line 104-112: The build_events method can pass nil details into
EmailLogEventDetails.build causing a NoMethodError; update build_events to
defensively pass an empty hash when e[:details] is nil (i.e., call
EmailLogEventDetails.build(e[:event_type], e[:details] || {})). Locate the
build_events function and change the details argument before constructing
EmailLogEvent so EmailLogEventDetails.build always receives a Hash.

---

Nitpick comments:
In `@lib/mailtrap/email_logs_api.rb`:
- Line 13: The file sets response_class = EmailLogMessage but that assignment is
unused because the class implements custom list and get methods instead of
leveraging BaseAPI's base_list/base_get; remove the unused response_class =
EmailLogMessage line from the EmailLogsAPI class (or alternatively add a concise
comment above it indicating it's intentional/for external introspection) so the
declaration no longer confuses readers — search for response_class,
EmailLogMessage, list, get, BaseAPI, base_list and base_get in the EmailLogsAPI
implementation to locate and apply the change.

In `@spec/mailtrap/email_log_event_details_spec.rb`:
- Around line 3-62: Add tests for the remaining event types by extending the
Mailtrap::EmailLogEventDetails spec: for each event_type 'open', 'spam',
'unsubscribe', 'reject', and 'suspension' call described_class.build(event_type,
payload) and assert the returned object is the appropriate struct
(described_class::Open, ::Spam, ::Unsubscribe, ::Reject, ::Suspension) and has
the expected attributes for that type (mirror the pattern used for
Delivery/Click/Bounce tests), using be_a(...) and have_attributes(...) to verify
attribute names and example values; keep the tests consistent with existing
examples so they validate .build behavior for all known DETAIL_STRUCTS.

In `@spec/mailtrap/email_log_message_spec.rb`:
- Around line 4-22: The spec's attributes hash used to initialize
EmailLogMessage is missing optional fields raw_message_url and events; update
the let(:attributes) hash in spec/mailtrap/email_log_message_spec.rb to include
raw_message_url (string URL) and events (array of event hashes or empty array)
so EmailLogMessage initialization and its handling of optional API response
fields are covered by the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1082f9c5-ad29-4678-bde9-9259a6a00386

📥 Commits

Reviewing files that changed from the base of the PR and between 75aa89a and 6a6ed2c.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • README.md
  • examples/email_logs_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/email_log_event.rb
  • lib/mailtrap/email_log_event_details.rb
  • lib/mailtrap/email_log_message.rb
  • lib/mailtrap/email_logs_api.rb
  • lib/mailtrap/email_logs_list_response.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/with_filters_and_search_after/sends_correct_query_params_and_maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/mailtrap/email_log_event_details_spec.rb
  • spec/mailtrap/email_log_event_spec.rb
  • spec/mailtrap/email_log_message_spec.rb
  • spec/mailtrap/email_logs_api_spec.rb
🚧 Files skipped from review as they are similar to previous changes (11)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • README.md
  • examples/email_logs_api.rb
  • lib/mailtrap/email_log_message.rb
  • spec/mailtrap/email_log_event_spec.rb
  • lib/mailtrap.rb
  • spec/mailtrap/email_logs_api_spec.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
  • lib/mailtrap/email_log_event_details.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • lib/mailtrap/email_log_event.rb

@mklocek mklocek requested a review from i7an March 16, 2026 15:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
lib/mailtrap/email_log_event_details.rb (1)

53-58: ⚠️ Potential issue | 🟠 Major

Guard hash.slice against nil details to avoid runtime crashes.

At Line 57, hash.slice(...) will raise when details is nil. Coerce to {} before slicing.

Proposed fix
 def self.build(event_type, hash)
   struct_class = DETAIL_STRUCTS[event_type.to_s]
   raise ArgumentError, "Unknown event_type: #{event_type.inspect}" unless struct_class

-  attrs = hash.slice(*struct_class.members)
+  details_hash = hash.is_a?(Hash) ? hash : {}
+  attrs = details_hash.slice(*struct_class.members)
   struct_class.new(**attrs)
 end
#!/bin/bash
# Verify whether API fixtures contain null details and confirm current call-path behavior.

set -euo pipefail

echo "1) Look for null event details in VCR fixtures:"
rg -n '"details"\s*:\s*null' spec/fixtures/vcr_cassettes || true

echo
echo "2) Confirm EmailLogsAPI passes event details directly into builder:"
rg -n -C2 'EmailLogEventDetails\.build\(e\[:event_type\], e\[:details\]\)' lib/mailtrap/email_logs_api.rb

echo
echo "3) Confirm builder currently slices hash without nil coercion:"
rg -n -C2 'def self\.build|hash\.slice' lib/mailtrap/email_log_event_details.rb
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mailtrap/email_log_event_details.rb` around lines 53 - 58, The build
method in EmailLogEventDetails uses hash.slice(...) which will raise if the
passed-in hash is nil; update the code in self.build to coerce the incoming
hash/details to an empty hash (e.g., using hash = (hash || {}) or hash.to_h)
before calling slice so attrs = hash.slice(*struct_class.members) is safe;
ensure you still raise the ArgumentError when DETAIL_STRUCTS[event_type.to_s] is
missing and construct the struct_class.new(**attrs) as before.
spec/mailtrap/email_logs_api_spec.rb (1)

6-7: ⚠️ Potential issue | 🟡 Minor

Make this VCR spec deterministic by removing ENV-driven IDs in expectations.

Line 6 can drift from the hardcoded path on Lines 55-56, which can cause cassette mismatches/flaky specs. Use a fixed account_id in this file and interpolate it in the expectation path.

Proposed fix
-  let(:account_id) { ENV.fetch('MAILTRAP_ACCOUNT_ID', 1_111_111) }
-  let(:client) { Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_API_KEY', 'local-api-key')) }
+  let(:account_id) { 1_111_111 }
+  let(:client) { Mailtrap::Client.new(api_key: 'local-api-key') }

@@
-        expect(client).to have_received(:get).with(
-          '/api/accounts/1111111/email_logs',
+        expect(client).to have_received(:get).with(
+          "/api/accounts/#{account_id}/email_logs",
           {

Also applies to: 55-56

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/mailtrap/email_logs_api_spec.rb` around lines 6 - 7, The spec is
non-deterministic because account_id is pulled from ENV; replace the
ENV.fetch('MAILTRAP_ACCOUNT_ID', 1_111_111) usage by setting a fixed literal
account_id (e.g. 1111111) in the spec and update any expectations that build the
request path (the expectation that references the path using account_id around
the Mailtrap::Client call) to interpolate that same fixed account_id so the VCR
cassette path matches deterministically; ensure the let(:account_id) value and
the expectation path referencing account_id (used with client/EmailLogs API
requests) use the identical fixed value.
🧹 Nitpick comments (1)
spec/mailtrap/email_log_message_spec.rb (1)

4-29: This spec doesn't cover the full DTO surface.

raw_message_url and events are part of Mailtrap::EmailLogMessage, but they aren't included in attributes, so the example name currently overstates what it verifies. Add them here or rename the example to reflect a partial attribute check.

♻️ Suggested update
       template_variables: {},
       opens_count: 2,
-      clicks_count: 1
+      clicks_count: 1,
+      raw_message_url: 'https://example.com/raw/test-message.eml',
+      events: [
+        Mailtrap::EmailLogEvent.new(
+          event_type: 'delivery',
+          created_at: '2025-01-15T10:30:01Z'
+        )
+      ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/mailtrap/email_log_message_spec.rb` around lines 4 - 29, The spec's
attributes hash used to initialize Mailtrap::EmailLogMessage is missing
raw_message_url and events, so change the attributes used by described_class.new
(the let(:attributes) hash) to include keys for raw_message_url and events with
representative values, ensuring the subject(:message) test can truly assert all
DTO fields via expect(message).to have_attributes(attributes); alternatively, if
you prefer not to add them, update the example description (the it string) to
indicate it checks only a subset of attributes rather than "all attributes".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/email_logs_api.rb`:
- Around line 34-35: The second-page call drops the original filters so
pagination no longer uses the same result set; update the follow-up call to pass
both the original filters and the cursor by calling email_logs.list with filters
plus search_after: response.next_page_cursor (use the same filters
object/variable you used in the first request) so the request includes
filters[<field>][operator], filters[<field>][value] alongside search_after;
locate the pagination example that assigns response and modify the subsequent
email_logs.list invocation to include the filters parameter together with
search_after.
- Line 1: Add the missing stdlib require so Time#iso8601 is available: at the
top of examples/email_logs_api.rb, alongside the existing require 'mailtrap',
add require 'time' so the calls to Time.now.utc.iso8601 (used around the
Time.now.utc.iso8601 occurrences) won’t raise NoMethodError when the example
runs.

In `@lib/mailtrap/email_log_message.rb`:
- Around line 14-18: Update the YARD attribute docs for the nullable hash fields
so the DTO matches actual responses: change the `@attr_reader` types for
custom_variables and template_variables from "Hash" to "Hash, nil" in
email_log_message.rb (the attribute doc comments for custom_variables and
template_variables), leaving other annotations (e.g., template_id) unchanged.

---

Duplicate comments:
In `@lib/mailtrap/email_log_event_details.rb`:
- Around line 53-58: The build method in EmailLogEventDetails uses
hash.slice(...) which will raise if the passed-in hash is nil; update the code
in self.build to coerce the incoming hash/details to an empty hash (e.g., using
hash = (hash || {}) or hash.to_h) before calling slice so attrs =
hash.slice(*struct_class.members) is safe; ensure you still raise the
ArgumentError when DETAIL_STRUCTS[event_type.to_s] is missing and construct the
struct_class.new(**attrs) as before.

In `@spec/mailtrap/email_logs_api_spec.rb`:
- Around line 6-7: The spec is non-deterministic because account_id is pulled
from ENV; replace the ENV.fetch('MAILTRAP_ACCOUNT_ID', 1_111_111) usage by
setting a fixed literal account_id (e.g. 1111111) in the spec and update any
expectations that build the request path (the expectation that references the
path using account_id around the Mailtrap::Client call) to interpolate that same
fixed account_id so the VCR cassette path matches deterministically; ensure the
let(:account_id) value and the expectation path referencing account_id (used
with client/EmailLogs API requests) use the identical fixed value.

---

Nitpick comments:
In `@spec/mailtrap/email_log_message_spec.rb`:
- Around line 4-29: The spec's attributes hash used to initialize
Mailtrap::EmailLogMessage is missing raw_message_url and events, so change the
attributes used by described_class.new (the let(:attributes) hash) to include
keys for raw_message_url and events with representative values, ensuring the
subject(:message) test can truly assert all DTO fields via expect(message).to
have_attributes(attributes); alternatively, if you prefer not to add them,
update the example description (the it string) to indicate it checks only a
subset of attributes rather than "all attributes".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37c8c80a-88f4-4028-a95a-0a2a7c8adb68

📥 Commits

Reviewing files that changed from the base of the PR and between 6a6ed2c and 7bea2d7.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • README.md
  • examples/email_logs_api.rb
  • lib/mailtrap.rb
  • lib/mailtrap/email_log_event.rb
  • lib/mailtrap/email_log_event_details.rb
  • lib/mailtrap/email_log_message.rb
  • lib/mailtrap/email_logs_api.rb
  • lib/mailtrap/email_logs_list_response.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/maps_response_data_to_EmailLogMessage_with_events_and_raw_message_url.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/with_filters_and_search_after/sends_correct_query_params_and_maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/mailtrap/email_log_event_details_spec.rb
  • spec/mailtrap/email_log_event_spec.rb
  • spec/mailtrap/email_log_message_spec.rb
  • spec/mailtrap/email_logs_api_spec.rb
✅ Files skipped from review due to trivial changes (1)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_get/when_message_not_found/raises_error.yml
🚧 Files skipped from review as they are similar to previous changes (5)
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • CHANGELOG.md
  • lib/mailtrap.rb
  • spec/fixtures/vcr_cassettes/Mailtrap_EmailLogsAPI/_list/with_filters_and_search_after/sends_correct_query_params_and_maps_response_data_to_EmailLogsListResponse_with_messages.yml
  • spec/mailtrap/email_log_event_details_spec.rb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants